/**
 * 
 */
package com.globallogic.training.university;

import java.util.*;

import com.globallogic.training.university.people.*;
import com.globallogic.training.university.study.*;
import com.globallogic.training.university.property.*;

/**
 * <code>Schedule</code> class represents a general schedule. It has information
 * about times of lectures, assigned teachers, students and auditoriums.
 * 
 * @author vasiliy.kulgaviy
 * 
 */
public class Schedule {
	private SortedSet<ScheduledItem> scheduledItems = new TreeSet<ScheduledItem>();

	private static Random randomGenerator = new Random();

	/**
	 * Schedule courses and teachers to auditoriums
	 * 
	 * @param courses
	 *            courses to schedule
	 * @param auditoriums
	 *            available auditoriums
	 * @param teachers
	 *            teachers. Should include teachers of the courses
	 */
	public void scheduleCourses(List<Course> courses, List<Auditorium> auditoriums,
			List<Teacher> teachers) {
		for (Course course : courses) {
			Teacher assignedTeacher = findTeacher(course, teachers);

			Auditorium auditorium1 = selectAuditorium(auditoriums,
					WeekDay.MONDAY, course, assignedTeacher);

			if (auditorium1 == null) {
				System.out.printf(Strings.ERROR_NO_FREE_AUDITORIUM,
						course.toString());
				return;
			}

			ScheduledTime scheduledTime1 = auditorium1.schedule();

			WeekDay nextWeekDay = getNextWeekDay(scheduledTime1.getWeekday());

			Auditorium auditorium2 = selectAuditorium(auditoriums, nextWeekDay,
					course, assignedTeacher);

			if (auditorium2 == null) {
				System.out.printf(Strings.ERROR_NO_FREE_AUDITORIUM,
						course.toString());
				return;
			}

			ScheduledTime scheduledTime2 = auditorium2.schedule();

			scheduledItems.add(new ScheduledItem(scheduledTime1, course,
			auditorium1, assignedTeacher));
			scheduledItems.add(new ScheduledItem(scheduledTime2, course,
			auditorium2, assignedTeacher));

			refreshAuitoriums(auditoriums);

		}
	}

	/**
	 * Assigns students to scheduled courses
	 * 
	 * @param students
	 */
	public void assignStudents(List<Student> students) {
		for (Student student : students) {
			for (ScheduledItem scheduledItem : scheduledItems) {
				Course currentCourse = scheduledItem.getCourse();

				if (currentCourse.getGrade() == student.getGrade()) {
					scheduledItem.addStudent(student);
				}
			}
		}
	}

	/**
	 * Prints General Schedule to console
	 */
	public void printSchedule() {
		System.out.println(Strings.TITLE_SCHEDULE_GENERAL);

		for (WeekDay weekDay : WeekDay.values()) {
			boolean isWeekDayPrinted = false;

			for (int hour : ScheduledTime.getSchedulableHours()) {
				boolean isHourPrinted = false;

				ScheduledTime currentSchedueldTime = new ScheduledTime(weekDay,
						hour);
				for (ScheduledItem scheduledItem : scheduledItems) {

					if (currentSchedueldTime.equals(scheduledItem
							.getScheduledTime())) {

						printWeekDayHeader(isWeekDayPrinted, weekDay);
						isWeekDayPrinted = true;

						printHourHeader(isHourPrinted, hour);
						isHourPrinted = true;

						System.out.println(scheduledItem.toString());
					}
				}
			}
		}
	}

	/**
	 * Prints a schedule of the student to console
	 * 
	 * @param student
	 *            student whose schedule to print
	 */
	public void printSchedule(Student student) {
		System.out.printf(Strings.TITLE_SCHEDULE_STUDENT, student.toString(),
				student.getGrade());

		for (WeekDay weekDay : WeekDay.values()) {
			boolean isWeekDayPrinted = false;

			for (int hour : ScheduledTime.getSchedulableHours()) {
				boolean isHourPrinted = false;

				ScheduledTime currentSchedueldTime = new ScheduledTime(weekDay,
						hour);
				for (ScheduledItem scheduledItem : scheduledItems) {

					boolean scheduledItemShouldBePrinted = scheduledItem
							.hasStudent(student)
							&& currentSchedueldTime.equals(scheduledItem
									.getScheduledTime());

					if (scheduledItemShouldBePrinted) {

						printWeekDayHeader(isWeekDayPrinted, weekDay);
						isWeekDayPrinted = true;

						printHourHeader(isHourPrinted, hour);
						isHourPrinted = true;

						System.out.println(scheduledItem.toString());
					}
				}
			}
		}
	}

	/**
	 * Prints a schedule to the teacher to console
	 * 
	 * @param teacher
	 *            teacher whose schedule to print
	 */
	public void printSchedule(Teacher teacher) {
		System.out.printf(Strings.TITLE_SCHEDULE_TEACHER, teacher.toString());

		Course[] teacherCourses = teacher.getCourses();
		refreshCourses(teacherCourses);

		int indexOfWeek = 1;

		while (true) {
			boolean isWeekPrinted = false;

			for (WeekDay weekDay : WeekDay.values()) {
				boolean isWeekDayPrinted = false;

				for (int hour : ScheduledTime.getSchedulableHours()) {
					boolean isHourPrinted = false;

					ScheduledTime currentSchedueldTime = new ScheduledTime(
							weekDay, hour);

					for (ScheduledItem scheduledItem : scheduledItems) {

						boolean scheduledItemShouldBePrinted = teacher
								.equals(scheduledItem.getTeacher())
								&& currentSchedueldTime.equals(scheduledItem
										.getScheduledTime());

						if (scheduledItemShouldBePrinted) {

							Course currentCourse = scheduledItem.getCourse();
							teacherCourses = updateTeacherCourses(
									teacherCourses, currentCourse);

							if (teacherCourses == null) {
								return;
							}

							if (currentCourse.getCurrentLesson() != null) {

								printWeekHeader(isWeekPrinted, indexOfWeek);
								isWeekPrinted = true;

								printWeekDayHeader(isWeekDayPrinted, weekDay);
								isWeekDayPrinted = true;

								printHourHeader(isHourPrinted, hour);
								isHourPrinted = true;

								System.out.println(scheduledItem.toString());
								System.out.println(currentCourse
										.getCurrentLesson().toString());
								currentCourse.incrementCurrenLessonIndex();
							}
						}
					}
				}
			}

			indexOfWeek++;
		}
	}

	private Auditorium selectAuditorium(List<Auditorium> auditoriums,
			WeekDay weekDay, Course course, Teacher assignedTeacher) {
		int startAuditoiumIndex = randomGenerator.nextInt(auditoriums.size());

		for (int index = startAuditoiumIndex; index < auditoriums.size(); index++) {
			if (prepareAuditorium(auditoriums.get(index), weekDay, course, assignedTeacher)) {
				return auditoriums.get(index);
			}
		}

		for (int index = 0; index < startAuditoiumIndex; index++) {
			if (prepareAuditorium(auditoriums.get(index), weekDay, course, assignedTeacher)) {
				return auditoriums.get(index);
			}
		}

		return null;
	}

	private boolean prepareAuditorium(Auditorium auditorium, WeekDay weekDay,
			Course course, Teacher assignedTeacher) {
		ScheduledTime possibleScheduledTime = auditorium
				.selectScheduledTime(weekDay);

		while (possibleScheduledTime != null) {
			if (scheduledItems.size() == 0) {
				return true;
			}

			for (Iterator<ScheduledItem> itr = scheduledItems.iterator(); itr
					.hasNext();) {
				ScheduledItem currentScheduledItem = itr.next();
				
				boolean timeIsScheduledForGradeOrTeacher = possibleScheduledTime
						.equals(currentScheduledItem.getScheduledTime())
						&& (course.getGrade() == currentScheduledItem
								.getCourse().getGrade() 
						|| assignedTeacher.equals(currentScheduledItem.getTeacher())) ;

				if (timeIsScheduledForGradeOrTeacher) {
					break;
				} else if (!itr.hasNext()) {
					return true;
				}
			}

			possibleScheduledTime = auditorium.selectScheduledTime(weekDay);
		}

		return false;
	}

	private WeekDay getNextWeekDay(WeekDay weekDay) {
		if (weekDay == WeekDay.MONDAY || weekDay == WeekDay.TUESDAY
				|| weekDay == WeekDay.WEDNESDAY) {
			return WeekDay.getValue(WeekDay.getIntValue(weekDay) + 2);
		} else {
			return WeekDay.getValue(WeekDay.getIntValue(weekDay) - 3);
		}
	}

	private Course[] updateTeacherCourses(Course[] coursesList,
			Course currentCourse) {
		Lessonable currentLesson = currentCourse.getCurrentLesson();

		if (currentLesson == null) {
			for (int i = 0; i < coursesList.length; i++) {
				if (currentCourse.equals(coursesList[i])) {
					coursesList[i] = null;
				}
			}
		}

		for (Course course : coursesList) {
			if (course != null) {
				return coursesList;
			}
		}

		return null;
	}

	private Teacher findTeacher(Course course, List<Teacher> Teachers) {
		for (Teacher teacher : Teachers) {
			if (teacher.hasCourse(course)) {
				return teacher;
			}
		}

		return null;
	}

	private static void refreshCourses(Course[] courses) {
		for (Course course : courses) {
			course.refreshCurrentLessonIndex();
		}
	}

	private static void refreshAuitoriums(List<Auditorium> auditoriums) {
		for (Auditorium auditorium : auditoriums) {
			auditorium.refreshPossibleScheduledTime();
		}
	}

	private void printWeekHeader(boolean isWeekPrinted, int indexOfWeek) {
		if (!isWeekPrinted) {
			System.out.printf(Strings.TITLE_WEEK, indexOfWeek);
		}
	}

	private void printWeekDayHeader(boolean isWeekDayPrinted, WeekDay weekDay) {
		if (!isWeekDayPrinted) {
			System.out.println(weekDay.toString());
		}
	}

	private void printHourHeader(boolean isHourPrinted, int hour) {
		if (!isHourPrinted) {
			System.out.printf(Strings.TITLE_HOUR, hour);
		}
	}
}
